/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <algorithm>
#include <cstring>
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include <securec.h>

#include <ImageReaderSource.h>
#include <zxing/common/Counted.h>
#include <zxing/Binarizer.h>
#include <zxing/MultiFormatReader.h>
#include <zxing/Result.h>
#include <zxing/ReaderException.h>
#include <zxing/common/GlobalHistogramBinarizer.h>
#include <zxing/common/HybridBinarizer.h>
#include <exception>
#include <zxing/Exception.h>
#include <zxing/common/IllegalArgumentException.h>
#include <zxing/BinaryBitmap.h>
#include <zxing/DecodeHints.h>

#include <zxing/qrcode/QRCodeReader.h>
#include <zxing/multi/qrcode/QRCodeMultiReader.h>
#include <zxing/multi/ByQuadrantReader.h>
#include <zxing/multi/MultipleBarcodeReader.h>
#include <zxing/multi/GenericMultipleBarcodeReader.h>
#include <jpgd.h>

#include "camera_kit.h"
#include "recorder.h"
#include "log.h"
#include "player.h"
#include "camera.h"


using namespace zxing;
using namespace zxing::multi;
using namespace zxing::qrcode;
using namespace jpgd;

using namespace std;
using namespace OHOS;
using namespace OHOS::Media;

static Player *player_ = nullptr;
static bool g_runAi = false;

static bool g_tryHarder = false;
static bool g_searchMulti = false;

static char g_wpaBuff[64] = {0};

static int GetStringInfo(char *buff, int bsize, const char *pstring, const char *key)
{
    char *ps = nullptr;
    char *pe = nullptr;
    int ret;

    if (buff == nullptr || bsize <= 0 || pstring == nullptr || key == nullptr) {
        return -1;
    }

    ps = strstr(const_cast<char *>(pstring), key);
    if (ps == nullptr) {
        return -1;
    }
    ps += strlen(key);
    pe = strchr(ps, ';');
    if (pe == nullptr) {
        ret = strncpy_s(buff, bsize, ps, strlen(ps));
    } else {
        ret = strncpy_s(buff, bsize, ps, pe - ps);
    }

    return ret;
}

int GetScanSsidPassword(WifiInfo &wifi, char *gpBuf, int size)
{
    const char *keySsid = "ssid:";
    const char *keyPwd = "password:";
    const char *keyGroup = "group:";

    if (strlen(g_wpaBuff) == 0) {
        SAMPLE_ERROR("can't scan ssid");
        return -1;
    }

    if (GetStringInfo(wifi.ssid, sizeof(wifi.ssid), static_cast<const char *>(g_wpaBuff), keySsid) < 0) {
        SAMPLE_ERROR("can't get ssid info!");
        return -1;
    }

    if (GetStringInfo(wifi.pwd, sizeof(wifi.pwd), static_cast<const char *>(g_wpaBuff), keyPwd) < 0) {
        SAMPLE_ERROR("can't get password info!");
        return -1;
    }

    if (GetStringInfo(gpBuf, size, static_cast<const char *>(g_wpaBuff), keyGroup) < 0) {
        SAMPLE_ERROR("can't get group info!");
        return -1;
    }

    if (memset_s(g_wpaBuff, sizeof(g_wpaBuff), 0, sizeof(g_wpaBuff)) < 0) {
        return -1;
    }

    return 0;
}

vector<Ref<Result> > decode(Ref<BinaryBitmap> image, DecodeHints hints)
{
    Ref<Reader> reader(new MultiFormatReader);
    return vector<Ref<Result>>(1, reader->decode(image, hints));
}

vector<Ref<Result> > decode_multi(Ref<BinaryBitmap> image, DecodeHints hints)
{
    MultiFormatReader delegate;
    GenericMultipleBarcodeReader reader(delegate);
    return reader.decodeMultiple(image, hints);
}


class TestPlayerCallback : public PlayerCallback {
    void OnPlaybackComplete() override
    {
        SAMPLE_INFO("PlayerCallback OnPlaybackComplete");
        if (player_==NULL) {
            return;
        }
        player_->Stop();
        player_->Release();
        delete player_;
        player_ = nullptr;
    }

    void OnError(int32_t errorType, int32_t errorCode) override
    {
        cout <<"PlayerCallback OnError" << endl;
    }
    void OnInfo(int type, int extra) override
    {
        cout <<"PlayerCallback OnInfo" << endl;
    }
    void OnRewindToComplete() override
    {
        cout <<"PlayerCallback OnRewindToComplete" << endl;
    }
    void OnVideoSizeChanged(int width, int height) override
    {
        cout <<"PlayerCallback OnVideoSizeChanged" << endl;
    }
};

void Beep(char *videoPath)
{
    int32_t ret;
    player_ = new Player();

    if (player_ == NULL) {
        SAMPLE_ERROR("player_ == NULL");
    }
    std::shared_ptr<PlayerCallback> callback = std::make_shared<TestPlayerCallback>();
    player_->SetPlayerCallback(callback);
    std::string uri(videoPath);
    Source source(uri);
    SAMPLE_INFO("run that");
    ret =  player_->SetSource(source);
    if (ret < 0) {
        SAMPLE_ERROR("player_->SetSource");
        return ;
    }
    SAMPLE_INFO("run that");
    ret = player_->Prepare();
    if (ret < 0) {
        SAMPLE_ERROR("player_->SetSource");
        return ;
    }
    SAMPLE_INFO("run that");
    Surface *surface = Surface::CreateSurface();
    if (surface == NULL) {
        SAMPLE_ERROR("Surface == NULL");
        return ;
    }
    SAMPLE_INFO("run that");
    ret = player_->SetVideoSurface(surface);
    if (ret < 0) {
        SAMPLE_ERROR("player_->SetSource");
        return ;
    }
    bool retb = player_->Play();
    if (!retb) {
        SAMPLE_ERROR(" Play false");
    }
    sleep(3);     // sleep 3 seconds for wait player running over!
    ret = player_->Stop();
    if (ret < 0) {
        SAMPLE_ERROR(" player_->Stop false");
        return ;
    }
    ret = player_->Release();
    SAMPLE_INFO("Release, ret:%d\n", ret);
    if (player_!=NULL) {
        delete player_;
        player_ = NULL;
    }
}

static void SampleSaveAiCapture(const char *p, uint32_t size)
{
    cout << "Start saving picture" << endl;
    struct timeval tv = {};
    gettimeofday(&tv, nullptr);
    struct tm *ltm = localtime(&tv.tv_sec);
    if (ltm != nullptr) {
        ofstream pic(IMG_PATH, ofstream::out | ofstream::trunc);
        cout << "write " << size << " bytes" << endl;
        pic.write(p, size);
        pic.close();
        cout << "Saving picture end" << endl;
    }
}

int read_image(Ref<LuminanceSource> source, bool hybrid, string expected)
{
    vector<Ref<Result>> results;
    string cell_result;
    int res = 0;

    try {
        Ref<Binarizer> binarizer;
        if (hybrid) {
            binarizer = new HybridBinarizer(source);
        } else {
            binarizer = new GlobalHistogramBinarizer(source);
        }
        DecodeHints hints(DecodeHints::DEFAULT_HINT);
        hints.setTryHarder(g_tryHarder);
        Ref<BinaryBitmap> binary(new BinaryBitmap(binarizer));
        if (g_searchMulti) {
            results = decode_multi(binary, hints);
        } else {
            results = decode(binary, hints);
        }
    } catch (const ReaderException& e) {
        cell_result = "zxing::ReaderException: " + string(e.what());
        res = -2; // error number 2
    }

    if (res != 0) {
        return res;
    }

    for (size_t i = 0; i < results.size(); i++) {
        if (sprintf_s(g_wpaBuff, sizeof(g_wpaBuff), "%s", results[i]->getText()->getText().c_str()) < 0) {
            return -1;
        }
    }

    return 0;
}

class SampleFrameStateCallback : public FrameStateCallback {
    void OnFrameFinished(Camera &camera, FrameConfig &fc, FrameResult &result) override
    {
        if (fc.GetFrameConfigType() == FRAME_CONFIG_CAPTURE) {
            list<Surface *> surfaceList = fc.GetSurfaces();
            for (Surface *surface : surfaceList) {
                SurfaceBuffer *buffer = surface->AcquireBuffer();
                if (buffer == nullptr) {
                    delete surface;
                    continue;
                }
                if (!g_runAi) {
                    int width, height;
                    int comps = 0;
                    unsigned char *virtAddr = reinterpret_cast<unsigned char *>(buffer->GetVirAddr());
                    char *bufferImg = reinterpret_cast<char*>(jpgd::decompress_jpeg_image_from_memory(
                        (const unsigned char *)virtAddr, buffer->GetSize(), &width, &height, &comps, 4));  // 4 is RGBA
                    zxing::ArrayRef<char> image = zxing::ArrayRef<char>(bufferImg, 4*width * height);
                    free(bufferImg);
                    if (!image) {
                        throw zxing::IllegalArgumentException("Loading jpeg_image_from_memory failed.");
                    }
                    Ref<LuminanceSource> source =
                        Ref<LuminanceSource>(new ImageReaderSource(image, width, height, comps));
                    read_image(source, true, "jpg");
                } else {
                    char *virtAddrOri = static_cast<char *>(buffer->GetVirAddr());
                    if (virtAddrOri != nullptr) {
                        SampleSaveAiCapture(virtAddrOri, buffer->GetSize());
                    }
                }
                surface->ReleaseBuffer(buffer);
                delete surface;
            }
        }
        delete &fc;
    }
};

class SampleCameraStateMng : public CameraStateCallback {
public:
    SampleCameraStateMng() = delete;
    explicit SampleCameraStateMng(EventHandler &eventHdlr) : eventHdlr_(eventHdlr) {}
    ~SampleCameraStateMng()
    {
    }
    void OnCreated(Camera &c) override
    {
        cout << "Sample recv OnCreate camera." << endl;
        auto config = CameraConfig::CreateCameraConfig();
        config->SetFrameStateCallback(&fsCb_, &eventHdlr_);
        c.Configure(*config);
        cam_ = &c;
    }
    void Capture()
    {
        if (cam_ == nullptr) {
            cout << "Camera is not ready." << endl;
            return;
        }
        FrameConfig *fc = new FrameConfig(FRAME_CONFIG_CAPTURE);
        Surface *surface = Surface::CreateSurface();
        if (surface == nullptr) {
            delete fc;
            cout << "CreateSurface failed" << endl;
            return;
        }
        surface->SetWidthAndHeight(1280, 720); /* 1280:width,720:height */
        fc->AddSurface(*surface);
        cam_->TriggerSingleCapture(*fc);
    }
    void Stop()
    {
        if (cam_ == nullptr) {
            cout << "Camera is not ready." << endl;
            return;
        }
        cam_->StopLoopingCapture();
    }

private:
    EventHandler &eventHdlr_;
    Camera *cam_ = nullptr;
    SampleFrameStateCallback fsCb_;
};

static EventHandler eventHdlr; // Create a thread to handle callback events
static SampleCameraStateMng g_camStateMng(eventHdlr);

int InitCamera(void)
{
    cout << "Camera sample begin." << endl;
    CameraKit *camKit = CameraKit::GetInstance();
    if (camKit == nullptr) {
        SAMPLE_ERROR("Can not get CameraKit instance");
        return -1;
    }

    list<string> camList = camKit->GetCameraIds();
    string camId;
    for (auto &cam : camList) {
        const CameraAbility *ability = camKit->GetCameraAbility(cam);
        /* find camera which fits user's ability */
        list<CameraPicSize> sizeList = ability->GetSupportedSizes(0);

        for (auto &pic : sizeList) {
            cout << "pic.width: " << pic.width << endl;
            cout << "pic.height: " << pic.height << endl;

            if (pic.width == 1280 && pic.height == 720) {   /* 1280:width,720:height */
                camId = cam;
                break;
            }
        }
    }

    if (camId.empty()) {
        SAMPLE_ERROR("No available camera.(1080p wanted)");
        return -1;
    }

    camKit->CreateCamera(camId, g_camStateMng, eventHdlr);
    return 0;
}

void RunQRCamera(void)
{
    g_runAi = false;
    if (memset_s(g_wpaBuff, sizeof(g_wpaBuff), 0, sizeof(g_wpaBuff)) < 0) {
        return;
    }
    g_camStateMng.Capture();

    g_camStateMng.Stop();
    SAMPLE_INFO("ran that");
}

void RunAICamera(void)
{
    g_runAi = true;
    g_camStateMng.Capture();
    g_camStateMng.Stop();
}
